package erwiki.api.rwc;

import java.security.Permission;
import java.security.Principal;
import java.util.HashMap;
import java.util.PropertyPermission;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.BooleanUtils;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.annotation.NonNull;
import org.elwiki.data.authorize.WikiPrincipal;
import org.elwiki.permissions.AllPermission;
import org.elwiki_data.WikiPage;

import erwiki.api.auth.AccountRegistry;
import erwiki.api.auth.AuthorizationManager;
import erwiki.api.core.Command;
import erwiki.api.core.ContextEnum;
import erwiki.api.core.Engine;
import erwiki.api.core.WikiContext;
import erwiki.api.core.WikiSession;
import erwiki.api.exceptions.NoSuchPrincipalException;
import erwiki.api.pages.Id2NamePage;
import erwiki.api.rwc.context.internal.service.ContextProviderW;
import erwiki.api.ui.CommandResolver;
import erwiki.api.ui.PageCommand;
import erwiki.api.ui.WikiCommand;
import erwiki.configuration.IWikiConfiguration;

/**
 * Runtime Wiki Context.</br>
 * Этот класс обеспечивает доступ к текущему контексту Wiki. Например, он обеспечивает доступ к
 * текущей WikiSession, и HTTP запросу.
 *
 * @see HttpServletRequest
 * @see HttpServletResponse
 */
public final class RWC implements WikiContext {

	public static final RWC INSTANCE = new RWC();

	@Deprecated
	private String shape;

	// Пока, это - для портирования WikiContext.
	@Deprecated
	private HashMap<String, Object> m_variableMap = new HashMap<>();

	private RWC() {
	}

	/**
	 * Возвращает экземпляр <code>Engine</code>, который представляет область контекста приложения
	 * ErWiki.
	 *
	 * @return Экземпляр {@link Engine}
	 */
	@Override
	public Engine getEngine() {
		checkContext();
		return ContextProviderW.getEngine();
	}

	/**
	 * Возвращает текущую WikiSession. Этот метод должен выполняться из потока Servlet-Request.
	 *
	 * @return Текущий экземпляр вики-сессии, никогда <code>null</code>.
	 * @throws IllegalStateException при вызове вне Servlet-Request потока.
	 */
	@Override
	public @NonNull WikiSession getWikiSession() {
		checkContext();
		return ContextProviderW.getWikiSession();
	}

	/**
	 * Возвращает <code>HttpServletRequest</code>, который обрабатывается в данный момент.
	 *
	 * @return Текущий обрабатываемый запрос
	 */
	public HttpServletRequest getRequest() {
		checkContext();
		return ContextProviderW.getRequest();
	}

	/**
	 * Возвращает <code>HttpServletResponse</code>, который будет отправлен клиенту после обработки
	 * текущего запроса.
	 *
	 * @return Экземпляр ответа, который будет отправлен клиенту
	 */
	public HttpServletResponse getResponse() {
		checkContext();
		return ContextProviderW.getResponse();
	}

	@Override
	public WikiPage getPage() {
		checkContext();
		return ContextProviderW.getPage();
	}

	@Override
	public String getPageId() {
		WikiPage wikiPage = getPage();
		return (wikiPage != null) ? wikiPage.getId() : null;
	}

	@Override
	public String getPageName() {
		WikiPage wikiPage = getPage();
		return (wikiPage != null) ? wikiPage.getName() : null;
	}

	@Override
	public int getPageVersion() {
		checkContext();
		return ContextProviderW.getPageVersion();
	}

	public void setPageVersion(int pageVersion) {
		checkContext();
		ContextProviderW.setPageVersion(pageVersion);
	}

	// TODO: пока, это - временная заглушка. НАДО РЕАЛИЗОВАТЬ, КАК БЫЛО В JSPWiki.
	@Override
	public WikiPage getRealPage() {
		checkContext();
		return ContextProviderW.getPage(); // :FVK: workaround.
	}

	@Override
	public void setPage(WikiPage wikiPage) {
		checkContext();
		ContextProviderW.setPage(wikiPage);
	}

	@Override
	public Command getCommand() {
		checkContext();
		return ContextProviderW.getCommand();
	}

	public void setCommand(Command command) {
		checkContext();
		ContextProviderW.setCommand(command);
	}

	private void checkContext() {
		if (!ContextProviderW.hasContext()) {
			throw new IllegalStateException("Invalid thread access");
		}
	}

	@Override
	public Command targetedCommand(Object target) {
		Command command = getCommand();
		if (command.getTarget() == null) {
			return command.targetedCommand(target);
		}
		return command;
	}

	@Override
	public ContextEnum getContextCmd() {
		return getCommand().getContextCmd();
	}

	@Override
	public String getContentTemplate() {
		return getCommand().getContentTemplate();
	}

	@Override
	public String getJSP() {
		return getCommand().getContentTemplate();
	}

	@Override
	public String getName() {
		Command command = getCommand();
		WikiPage wikiPage = getPage();
		if (command instanceof PageCommand) {
			return wikiPage != null ? wikiPage.getName() : "<no page>";
		}
		return command.getName();

	}

	@Override
	public String getRequestContext() {
		return getCommand().getRequestContext();
	}

	private static final Permission DUMMY_PERMISSION = new PropertyPermission("os.name", "read");

	@Override
	public Permission requiredPermission() {
		Command command = getCommand();
		Engine engine = getEngine();
		// This is a filthy rotten hack -- absolutely putrid
		if (WikiCommand.INSTALL.equals(command)) {
			// See if admin users exists
			try {
				final AccountRegistry accountRegistry = engine.getManager(AccountRegistry.class);
				accountRegistry.findByLoginName("admin"); // :FVK: == Installer.ADMIN_ID
			} catch (final NoSuchPrincipalException e) {
				return DUMMY_PERMISSION;
			}

			return null; // TODO::FVK: new AllPermission(globalPrefs.getApplicationName(), null);
		}

		// TODO: we should really break the contract so that this
		// method returns null, but until then we will use this hack
		if (command.requiredPermission() == null) {
			return DUMMY_PERMISSION;
		}

		return command.requiredPermission();
	}

	@Override
	public Object getTarget() {
		return getCommand().getTarget();
	}

	@Override
	public String getURLPattern() {
		return getCommand().getURLPattern();
	}

	@Override
	public void pushRealPage(WikiPage wikiPage) {
		// TODO Auto-generated method stub
		Assert.isLegal(false, "нет кода.");
	}

	@Override
	public void popRealPage() {
		// TODO Auto-generated method stub
		Assert.isLegal(false, "нет кода.");
	}

	@Override
	public String getPageName(String pageId) {
		String pageName = null;
		Id2NamePage id2NamePage = getEngine().getManager(Id2NamePage.class);
		if (id2NamePage != null) {
			pageName = id2NamePage.getName(pageId);
		}
		return pageName;
	}

	@Override
	@Deprecated
	public IWikiConfiguration getConfiguration() {
		return getEngine().getWikiConfiguration();
	}

	@Override
	public void setRequestContext(String context) {
		updateCommand(context);
	}

	@Override
	public <T> T getVariable(String key) {
		return (T) m_variableMap.get(key);
	}

	@Override
	public void setVariable(String key, Object data) {
		m_variableMap.put(key, data);
		updateCommand(getCommand().getRequestContext());
	}

	@Override
	public boolean getBooleanWikiProperty(String key, boolean defValue) {
		var value = BooleanUtils.toBooleanObject((String) getVariable(key));

		IWikiConfiguration wikiConfiguration = getEngine().getWikiConfiguration();
		return (value != null) ? value : wikiConfiguration.getBooleanProperty(key, defValue);
	}

	@Override
	public String getHttpParameter(String paramName) {
		return getRequest().getParameter(paramName);
	}

	@Override
	public HttpServletRequest getHttpRequest() {
		return getRequest();
	}

	@Override
	public void setShape(String dir) {
		this.shape = dir;
	}

	@Override
	public String getShape() {
		return this.shape;
	}

	@Override
	public Principal getCurrentUser() {
		@NonNull
		WikiSession m_session = getWikiSession();
		if (m_session == null) {// :FVK: - лишнее.
			// This shouldn't happen, really...
			return WikiPrincipal.GUEST;
		}
		return m_session.getUserPrincipal();
	}

	@Deprecated // перенесено в AuthorizationManager.
	@Override
	public boolean hasAdminPermissions() {
		Engine engine = getEngine();
		AuthorizationManager authorizationManager = engine.getManager(AuthorizationManager.class);
		return authorizationManager.checkPermission(getWikiSession(),
				new AllPermission(engine.getApplicationName(), null));
	}

	@Override
	public String getRedirectURL() {
		WikiPage wikiPage = getPage();
		final String pagename = wikiPage.getName();
		String redirectURL = null; // :FVK: WikiEngine.getCommandResolver().getSpecialPageReference(pagename);
		if (redirectURL == null) {
			final String alias = wikiPage.getAlias();
			/* TODO: :FVK: в `if` - добавил `&& !alias.isEmpty()` - чтоб не рестартовал вход через Wiki.jsp, без указания страницы. */
			if (alias != null && !alias.isEmpty()) {
				redirectURL = getViewURL(alias);
			} else {
				redirectURL = wikiPage.getRedirect();
			}
		}

		return (redirectURL == null || redirectURL.isEmpty()) ? null : redirectURL;
	}

	/**
	 * Protected method that updates the internally cached Command. Will always be called when the page
	 * name, request context, or variable changes.
	 *
	 * @param requestContext the desired request context
	 * @since 2.4
	 */
	protected void updateCommand(final String requestContext) {
		Command command;
		if (requestContext == null) {
			command = PageCommand.NONE;
		} else {
			CommandResolver commandResolver = getEngine().getManager(CommandResolver.class);
			command = commandResolver.findCommand(getRequest(), requestContext);
		}

		WikiPage page = getPage();
		if (command instanceof PageCommand && page != null) {
			command = command.targetedCommand(page);
		}
		setCommand(command);
	}

	public void pushContext(Command command) {
		checkContext();
		ContextProviderW.pushCommand();
	}

	public void popContext() {
		checkContext();
		ContextProviderW.popCommand();
	}

}
